[기능구현#3] 로그인 기능
실행 환경
같은 폴더의 “[리팩터링] 게시판 프로젝트 아키텍처” 노트에 명시된 스택 기준 — Spring Boot 3.3.5, Java 17, H2 Database, Thymeleaf.
로그인 서비스 흐름도
[Client 요청]
│
▼
1차 인증 (Filter, order=2)
- Session 존재 여부 확인 (getSession(false))
- 없으면 → /members/login으로 리다이렉트
│ 통과
▼
2차 인증 (Interceptor, order=2)
- Session의 Member와 URL의 memberId 일치 여부 확인 (정규표현식)
- 불일치 시 → /members/login으로 리다이렉트
│ 통과
▼
[Controller] → [LoginService] → 로그인 성공 시 Session 생성/저장로그인 인증)
- 로그인 인증의 경우, Filter와 Interceptor를 이용하여 구현할 수 있다
- Filter의 경우, spring의 Distpatcher에 들어가기 전에 필터링이 가능하므로 주로 Filter에서 로그인 인증이 일어난다
- 현재 프로젝트 적용
- 1차 로그인 인증 (Filter)
- Session값의 유무를 통해, Session을 가지고 있으면 Filter 통과
- 2차 로그인 인증 (Interceptor)
- Session 값 유무 판단 + URL 변경을 통한 사용자 인증을 막고자 정규표현식을 이용한 필터링 진행
- ex) http://localhost/members/test1 -> http://localhost/members/admin (이동 방지)
- 1차 로그인 인증 (Filter)
로그인 인증 방법
- JWT
- Session
- JWT
- JWT의 경우, 멤버 객체의 특정 값들을 조합하여 토큰을 발행 후, 클라이언트에게 전달하여 인증하는 방법
- 장점 : 서버의 저장 리소스를 줄일 수 있음 (클라이언트가 저장하고 있기에) -> Session 방식과 차이점
- 단점 : 유효기간이 지나기 전까지는 클라이언트가 탈취를 당하더라도 해결하는 방법이 적절치 못함
- Session
- Session의 경우, 인스턴스당 session값을 매칭하여 session store에 서버가 저장한 후 인증 하는 방법
- 장점 : 세션 관리가 용이함
- 단점 : 서버의 리소스를 소모하므로, 너무 많은 인원의 경우 저장하는데 많은 리소스를 소모할 수 있음
- 현재 프로젝트 적용
- Http Session을 사용하여, member 객체의 정보를 이용해 session 생성 후 인증진행
주요 구현 부분
- Filter 인증 부분
- getSession(false)를 통해, 세션이 있는지 확인 후 없으면 로그인화면으로 리다이렉트 진행
try{
log.info("인증체크 필터 시작 {}", requestURI);
if(loginCheckPath(requestURI)){
log.info("인증체크 로직 실행 {} ", requestURI);
HttpSession session = httpRequest.getSession(false);
if (session == null || session.getAttribute(SessionConst.LOGIN_MEMBER) == null){
log.info("미인증 사용자 요청 {}", requestURI);
httpResponse.sendRedirect("/members/login?redirectURL=" + requestURI);
return;
}
}
chain.doFilter(request,response);
}catch (Exception e){
throw e;
}finally{
log.info("인증 체크 필터 종료 {} ", requestURI);
}- Interceptor 인증 부분
- session값에 저장된 Member와 URL에 나와있는 memberId와 다를 경우, 로그인화면으로 리다이렉트
if(matcher.find()){
String account = matcher.group(1);
Member authAccount = (Member) session.getAttribute(SessionConst.LOGIN_MEMBER);
if (authAccount.getLoginId() == null || !authAccount.getLoginId().equals(account)){
response.sendRedirect("/members/login?redirectURI="+requestURI);
log.info("Session loginId: {}, URL account: {}", authAccount.getLoginId(), account);
return false;
}- Filter와 Interceptor 등록
@Bean
public FilterRegistrationBean loginCheckFilter(){
FilterRegistrationBean<Filter> filterFilterRegistrationBean = new FilterRegistrationBean<>();
filterFilterRegistrationBean.setFilter(new LoginCheckFilter());
filterFilterRegistrationBean.setOrder(2);
filterFilterRegistrationBean.addUrlPatterns("/*");
filterFilterRegistrationBean.setDispatcherTypes(DispatcherType.REQUEST);
return filterFilterRegistrationBean;
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LogInterceptor())
.order(1)
.addPathPatterns("/**")
.excludePathPatterns("/css/**", "/*.ico", "/error", "/errors/custom/**");
registry.addInterceptor(new LoginCheckInterceptor())
.order(2)
.addPathPatterns("/**")
.excludePathPatterns("/members/*/boards/*","/members/login", "/members/form", "/members/logout", "/css/**", "/*.ico", "/error","/errors/custom/**");
}- 로그인 인증 서비스 부분
- 로그인이 성공적으로 될경우, Session 발급
if (loginService.loginCheck(loginForm.getLoginId(), loginForm.getPasswd())){
HttpSession session = request.getSession(true);
Member loginMember= memberRepository.findByLoginId(loginForm.getLoginId());
session.setAttribute(SessionConst.LOGIN_MEMBER, loginMember);
return "redirect:/members/" + loginForm.getLoginId();
}아쉬운점
- HttpSession이 아닌, session store를 운용하지 못한점을 보완하고 싶다
- URL를 통한 계정 이동을 방지하고자, Interceptor에 추가하였지만, URL상에 개인정보가 유출되는 문제도 있고, 사용자가 너무 쉽게 유추하기가 쉬우므로 수정하고자 한다
관련 문서
- (학습/프로젝트/토이프로젝트/게시판 프로젝트) [리팩터링] 개발 요구사항 (게시판 프로젝트) — 개발 요구사항 문서에 명시된 로그인 기능을 구현
- (학습/프로젝트/토이프로젝트/게시판 프로젝트) [리팩터링] Session을 통한 로그인 처리 — 1차/2차 인증(Filter/Interceptor) 로그인 기능을 세션 저장소 구조로 리팩터링하는 후속 문서
- (학습/프로젝트/토이프로젝트/게시판 프로젝트) [트러블 슈팅] 로그인 인증 관련 문제 — 로그인 기능의 Interceptor 인증 로직에서 발생한 버그를 다루는 트러블슈팅 문서
- (학습/프레임워크/Spring Framework) Servlet Filter — 1차 인증에 사용된 Servlet Filter 개념을 다루는 강의 노트
- (학습/프레임워크/Spring Framework/실습_스프링MVC/로그인 구현) 로그인 구현 — Filter/Interceptor 기반 로그인 인증 구현이 다루는 것과 동일한 주제의 강의 노트
- (학습/프레임워크/Spring Framework/실습_스프링MVC) 로그인 기능 — JWT vs Session 로그인 방식 비교를 다루는 강의 노트와 동일한 주제